`define RST_CNT_END 20'h1ffff

module rcg (
    input        sys_clk_10k,
    input        sys_clk_25m,    // xc clock input at 25MHz
    input        sys_clk_100m,   // xc clock input at 100MHz
    input  [6:0] clk_cfg,
    input        sys_rst_n,
    input  [2:0] pll_cfg,
    // clock domain core (configurable)
    output       clk_core,
    output       rst_core_n,
    output       rst_10k_n,
    // clock domain hs_peri (100MHz)
    output       clk_hs_peri,
    output       rst_hs_peri_n,
    // clock domain peri (25mHz)
    output       clk_peri,
    output       rst_peri_n,
    // clk_core div by 4 for test
    output       clk_core_4div
);


  // connect to u0_pll
  reg         u0_pll_bp;
  reg  [ 5:0] u0_pll_refdiv;
  reg  [11:0] u0_pll_fbdiv;
  reg  [ 2:0] u0_pll_postdiv1;
  reg  [ 2:0] u0_pll_postdiv2;

  wire        u0_pll_lock;
  wire        u0_pll_clk;
  wire        u0_pll_clksscg;
  wire        u0_pll_foutvco;



  // configure PLL by pll_cfg
  always @(pll_cfg) begin
    case (pll_cfg)
      3'b000: begin  //bypass
        u0_pll_bp       = 1'b1;
        u0_pll_fbdiv    = 12'd80;
        u0_pll_refdiv   = 6'b000010;
        u0_pll_postdiv1 = 3'b001;
        u0_pll_postdiv2 = 3'b001;
      end
      3'b001: begin  //4*clk 100MHZ
        u0_pll_bp       = 1'b0;
        u0_pll_fbdiv    = 12'd80;
        u0_pll_refdiv   = 6'b000010;
        u0_pll_postdiv1 = 3'b101;
        u0_pll_postdiv2 = 3'b010;
      end
      3'b010: begin  //6*clk 150MHZ
        u0_pll_bp       = 1'b0;
        u0_pll_fbdiv    = 12'd96;
        u0_pll_refdiv   = 6'b000010;
        u0_pll_postdiv1 = 3'b100;
        u0_pll_postdiv2 = 3'b010;
      end
      3'b011: begin  //8*clk 200MHZ
        u0_pll_bp       = 1'b0;
        u0_pll_fbdiv    = 12'd80;
        u0_pll_refdiv   = 6'b000010;
        u0_pll_postdiv1 = 3'b101;
        u0_pll_postdiv2 = 3'b001;
      end
      3'b100: begin  //10*clk 250MHZ
        u0_pll_bp       = 1'b0;
        u0_pll_fbdiv    = 12'd80;
        u0_pll_refdiv   = 6'b000010;
        u0_pll_postdiv1 = 3'b010;
        u0_pll_postdiv2 = 3'b010;
      end
      3'b101: begin  //12*clk 300MHZ
        u0_pll_bp       = 1'b0;
        u0_pll_fbdiv    = 12'd96;
        u0_pll_refdiv   = 6'b000010;
        u0_pll_postdiv1 = 3'b010;
        u0_pll_postdiv2 = 3'b010;
      end
      3'b110: begin  //14*clk 350MHZ
        u0_pll_bp       = 1'b0;
        u0_pll_fbdiv    = 12'd112;
        u0_pll_refdiv   = 6'b000010;
        u0_pll_postdiv1 = 3'b010;
        u0_pll_postdiv2 = 3'b010;
      end
      3'b111: begin  //16*clk 400MHZ
        u0_pll_bp       = 1'b0;
        u0_pll_fbdiv    = 12'd64;
        u0_pll_refdiv   = 6'b000010;
        u0_pll_postdiv1 = 3'b010;
        u0_pll_postdiv2 = 3'b001;
      end
      default: begin  //bypass
        u0_pll_bp       = 1'b1;
        u0_pll_fbdiv    = 12'd60;
        u0_pll_refdiv   = 6'b000010;
        u0_pll_postdiv1 = 3'b101;
        u0_pll_postdiv2 = 3'b110;
      end
    endcase
  end

  // PLL instances
  //
  // Algorithm for output frequency
  // CLK_OUT = XIN * (M + F) / (N * NO)
  // NO = 2^OD
  //
  // !Please keep these conditions through usage
  // 1. 5MHz <= XIN / N <= 50MHz
  // 2. 200MHz <= CLK_OUT * NO <= 700MHz
  // 3. M >= 7, N >= 2.
  //    It is recommended to have M > 64 if fraction number
  //    is added for better jitter performance.
  //
  // In the implementation, F is **hardwired** to 0, and XIN = 25MHz,
  // thus we have
  //    CLK_OUT = 25MHz * M / (N * NO)

  // u0_pll for CPU cores
  PLLTS28HPMLAINT u0_pll (
      .BYPASS       (u0_pll_bp),
      .DSMPD        (1'b1),
      .FBDIV        (u0_pll_fbdiv),
      .FOUTPOSTDIVPD(1'b0),
      .FOUTVCOPD    (1'b0),
      .FREF         (sys_clk_25m),
      .PD           (1'b0),
      .POSTDIV1     (u0_pll_postdiv1),
      .POSTDIV2     (u0_pll_postdiv2),
      .REFDIV       (u0_pll_refdiv),
      .CLKSSCG      (u0_pll_clksscg),
      .FOUTPOSTDIV  (u0_pll_clk),
      .FOUTVCO      (u0_pll_foutvco),
      .LOCK         (u0_pll_lock)
  );

  // clock domain peri
  sync rst_peri_syn_n (
      .clk      (clk_peri),
      .rst_n    (sys_rst_n),
      .rst_syn_n(rst_peri_n)
  );

  sync rst_sys_clk_10k_syn_n (
      .clk      (sys_clk_10k),
      .rst_n    (sys_rst_n),
      .rst_syn_n(rst_10k_n)
  );

  // counter to wait until PLL gets stable
  reg  [19:0] rst_cnt;
  wire        rst_gen_n;
  always @(posedge clk_peri or negedge rst_peri_n) begin
    if (!rst_peri_n) rst_cnt <= 20'h0;
    else if (rst_cnt < `RST_CNT_END) rst_cnt <= rst_cnt + 20'h1;
  end
  assign rst_gen_n = `RST_CNT_END == rst_cnt;

  // clock domain core
  sync rst_core_syn_n (
      .clk      (clk_core),
      .rst_n    (rst_gen_n),
      .rst_syn_n(rst_core_n)
  );

  // clock domain mux_core
  wire mux_core_clk;
  wire rst_mux_core_n;
  sync rst_mux_core_syn_n (
      .clk      (mux_core_clk),
      .rst_n    (rst_gen_n),
      .rst_syn_n(rst_mux_core_n)
  );

  // clock domain hs_peri
  sync rst_hs_peri_syn_n (
      .clk      (clk_hs_peri),
      .rst_n    (rst_gen_n),
      .rst_syn_n(rst_hs_peri_n)
  );

  // clock domain sys_clk_100m
  wire rst_sys_clk_100_n;
  sync rst_sys_clk_100_syn_n (
      .clk      (sys_clk_100m),
      .rst_n    (sys_rst_n),
      .rst_syn_n(rst_sys_clk_100_n)
  );

  div4 div4_core_clk (
      .clk    (clk_core),
      .rst_n  (rst_core_n),
      .clk_out(clk_core_4div)
  );

  wire div4_sys_clk;
  div4 sys_clk_div4 (
      .clk    (sys_clk_100m),
      .rst_n  (rst_sys_clk_100_n),
      .clk_out(div4_sys_clk)
  );

  // assign Z = S ? I1 : I0;
  CKMUX2D4BWP40P140LVT u0_lvt_ckmux2hdv4 (
      .S (clk_cfg[6]),
      .I0(sys_clk_25m),
      .I1(div4_sys_clk),
      .Z (clk_peri)
  );
  CKMUX2D4BWP40P140LVT u1_lvt_ckmux2hdv4 (
      .S (clk_cfg[5]),
      .I0(sys_clk_100m),
      .I1(u0_pll_clk),
      .Z (mux_core_clk)
  );

  wire div2_mux_core_clk_hs;
  div2 mux_core_clk_hs_div2 (
      .clk    (mux_core_clk),
      .rst_n  (rst_mux_core_n),
      .clk_out(div2_mux_core_clk_hs)
  );

  wire div4_mux_core_clk_hs;
  div4 mux_core_clk_hs_div4 (
      .clk    (mux_core_clk),
      .rst_n  (rst_mux_core_n),
      .clk_out(div4_mux_core_clk_hs)
  );

  wire div_mux_core_clk_hs0;
  wire div_mux_core_clk_hs1;
  wire div_mux_core_clk_hs;
  CKMUX2D4BWP40P140LVT u2_lvt_ckmux2hdv4 (
      .S (clk_cfg[4]),
      .I0(div_mux_core_clk_hs0),
      .I1(div_mux_core_clk_hs1),
      .Z (div_mux_core_clk_hs)
  );
  CKMUX2D4BWP40P140LVT u3_lvt_ckmux2hdv4 (
      .S (clk_cfg[3]),
      .I0(div4_mux_core_clk_hs),
      .I1(mux_core_clk),
      .Z (div_mux_core_clk_hs1)
  );
  CKMUX2D4BWP40P140LVT u4_lvt_ckmux2hdv4 (
      .S (clk_cfg[3]),
      .I0(mux_core_clk),
      .I1(div2_mux_core_clk_hs),
      .Z (div_mux_core_clk_hs0)
  );

  wire div3_mux_core_clk;
  div3 mux_core_clk_div3 (
      .clk    (mux_core_clk),
      .rst_n  (rst_mux_core_n),
      .clk_out(div3_mux_core_clk)
  );

  CKMUX2D4BWP40P140LVT u5_lvt_ckmux2hdv4 (
      .S (clk_cfg[2]),
      .I0(div_mux_core_clk_hs),
      .I1(div3_mux_core_clk),
      .Z (clk_hs_peri)
  );

  wire div2_mux_core_clk;
  div2 mux_core_clk_div2 (
      .clk    (mux_core_clk),
      .rst_n  (rst_mux_core_n),
      .clk_out(div2_mux_core_clk)
  );

  wire div4_mux_core_clk;
  div4 mux_core_clk_div4 (
      .clk    (mux_core_clk),
      .rst_n  (rst_mux_core_n),
      .clk_out(div4_mux_core_clk)
  );

  wire clk_core0;
  wire clk_core1;
  CKMUX2D4BWP40P140LVT u6_lvt_ckmux2hdv4 (
      .S (clk_cfg[1]),
      .I0(clk_core0),
      .I1(clk_core1),
      .Z (clk_core)
  );
  CKMUX2D4BWP40P140LVT u7_lvt_ckmux2hdv4 (
      .S (clk_cfg[0]),
      .I0(div4_mux_core_clk),
      .I1(mux_core_clk),
      .Z (clk_core1)
  );
  CKMUX2D4BWP40P140LVT u8_lvt_ckmux2hdv4 (
      .S (clk_cfg[0]),
      .I0(mux_core_clk),
      .I1(div2_mux_core_clk),
      .Z (clk_core0)
  );

endmodule

// output 2-div clk for test
module div2 (
    clk,
    rst_n,
    clk_out
);
  input clk;
  input rst_n;
  output clk_out;

  reg [1:0] div2_cnt;
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) div2_cnt <= 1'b0;
    else div2_cnt <= div2_cnt + 1'b1;
  end
  assign clk_out = div2_cnt[0];
endmodule

// output 3-div clk for test
module div3 (
    clk,
    rst_n,
    clk_out
);
  input clk;
  input rst_n;
  output clk_out;
  reg [0:1] neg_count;
  reg [0:1] pos_count;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) pos_count <= 0;
    else if (pos_count == 2) pos_count <= 0;
    else pos_count <= pos_count + 1;
  end
  always @(negedge clk or negedge rst_n) begin
    if (!rst_n) neg_count <= 0;
    else if (neg_count == 2) neg_count <= 0;
    else neg_count <= neg_count + 1;
  end
  assign clk_out = (pos_count == 2) | (neg_count == 2);
endmodule


// output 4-div clk for test
module div4 (
    clk,
    rst_n,
    clk_out
);
  input clk;
  input rst_n;
  output clk_out;
  reg [1:0] div4_cnt;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) div4_cnt <= 2'b00;
    else div4_cnt <= div4_cnt + 2'b01;
  end
  assign clk_out = div4_cnt[1];
endmodule

module sync (
    clk,
    rst_n,
    rst_syn_n
);
  input clk;
  input rst_n;
  output rst_syn_n;
  reg r_rst_n1;
  reg r_rst_n2;
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      r_rst_n1 <= 1'b0;
      r_rst_n2 <= 1'b0;
    end else begin
      r_rst_n1 <= 1'b1;
      r_rst_n2 <= r_rst_n1;
    end
  end
  assign rst_syn_n = r_rst_n2;
endmodule
